#include "serial.h"
#include <interrupt/irq.h>
#include <system/mod.h>


module_t serial_module =
{
	.name = "serial\0",
	.author = "dak\0",
	.license = "gpl\0",
	.desc = "serial driver\0",
	.dep = "\0",
	.type = MOD_TYPE_DEV_NETWORK,
	
	.init = serial_init,
	.shut = serial_shut,
	//.read = serial_read,
	//.write = serial_write,
};

/*
 * Inizializza le porte seriali
 */
uint32_t serial_init()
{
	int x;
	
	serial_init_port(COM1);
	serial_init_port(COM2);
	serial_init_port(COM3);
	serial_init_port(COM4);
	
	for(x = 0; x < 4; x++)
		combuf[x].where = combuf[x].size = combuf[x].point = 0;
		
	irq_install_handler(IRQ_COM13, &serial_callback);
	irq_install_handler(IRQ_COM24, &serial_callback);
}
	
	
/*
 * Inizializza una porta seriale port
 */
void serial_init_port(uint32_t port)
{
	io_outb(port + 1, 0x00);    // Disable all interrupts
	io_outb(port + 3, 0x80);    // Enable DLAB (set baud rate divisor)
	io_outb(port + 0, 0x03);    // Set divisor to 3 (lo byte) 38400 baud
	io_outb(port + 1, 0x00);    //                  (hi byte)
	io_outb(port + 3, 0x03);    // 8 bits, no parity, one stop bit
	io_outb(port + 2, 0xC7);    // Enable FIFO, clear them, with 14-byte threshold
	io_outb(port + 4, 0x0B);    // IRQs enabled, RTS/DSR set
}


/*
 * Invia un dato ad una porta
 */
void serial_send(uint32_t port, uint8_t data)
{
	while ((io_inb(port + 5) & 0x20) == 0);

	io_outb(port, data);
}


/*
 * Legge un carattere dal buffer
 */
uint8_t serial_receive(uint32_t port)
{
	char ch = 0;
	switch(port)
	{
		case COM1:
			if(combuf[0].size > 0)
			{
				ch = combuf[0].buf[combuf[0].where];
				combuf[0].where++;
				combuf[0].size--;
			}
			return ch;
					
		case COM2:
			if(combuf[1].size > 0)
			{
				ch = combuf[1].buf[combuf[1].where];
				combuf[1].where++;
				combuf[1].size--;
			}
			return ch;
			
		case COM3:
			if(combuf[2].size > 0)
			{
				ch = combuf[2].buf[combuf[2].where];
				combuf[2].where++;
				combuf[2].size--;
			}
			return ch;
			
		case COM4:
			if(combuf[3].size > 0)
			{
				ch = combuf[3].buf[combuf[3].where];
				combuf[3].where++;
				combuf[3].size--;
			}
			return ch;
	}
}


/*
 * Handler delle interruzioni per le porte com
 */
static void serial_callback(regs_t *reg)
{
	if((io_inb(COM1 + 5) & 1) != 0)
	{
		combuf[0].buf[combuf[0].point] = io_inb(COM1);
		combuf[0].size++;
		combuf[0].point++;
	}	
	if((io_inb(COM2 + 5) & 1) != 0)
	{
		combuf[1].buf[combuf[1].point] = io_inb(COM2);
		combuf[1].size++;
		combuf[1].point++;
	}		
	if((io_inb(COM3 + 5) & 1) != 0)
	{
		combuf[2].buf[combuf[2].point] = io_inb(COM3);
		combuf[2].size++;
		combuf[2].point++;
	}		
	if((io_inb(COM4 + 5) & 1) != 0)
	{
		combuf[3].buf[combuf[3].point] = io_inb(COM4);
		combuf[3].size++;
		combuf[3].point++;
	}		
}



	//printf("Serial data: ");
	//while(1)
	//{
	//	char c = serial_receive(COM1);
	//	if(c != 0) putchar(c);
	//}


/*
 * Nulla gli handlers
 */
uint32_t serial_shut()
{
	irq_install_handler(IRQ_COM13, NULL);
	irq_install_handler(IRQ_COM24, NULL);
}


/*
 * Scrive in un buffer tot dati ricevuti da una porta
 */
uint32_t serial_read(struct file *f, uint8_t *data, uint32_t size, uint64_t off)
{
	int i = 0;
	char c;
	
	if(off > 0 && off < 5)
		while(((c = serial_receive(off)) != 0) && (i < size))
		{
			data[i] = c;
			i++;
		} 

	return i;
}


/*
 * Scrive nella porta desiderata un buffer di chars xD
 */
uint32_t serial_write(struct file *f, uint8_t *data, uint32_t size, uint64_t off)
{
	int i = 0;
	char c;
	
	if(off > 0 && off < 5)
		while(i < size)
		{
			serial_send(off, data[i]);
			i++;
		}
	
	return i;
}
